package pl.agh.pp.zephyr.web.tree;

import java.io.Serializable;
import java.util.Enumeration;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;

import pl.agh.pp.zephyr.logic.Engine;
import pl.agh.pp.zephyr.model.Gatekeeper;
import pl.agh.pp.zephyr.model.H323Area;
import pl.agh.pp.zephyr.model.Mcu;
import pl.agh.pp.zephyr.model.Room;
import pl.agh.pp.zephyr.model.User;
import pl.agh.pp.zephyr.web.Tabs;

import com.icesoft.faces.component.dragdrop.DropEvent;
import com.icesoft.faces.component.ext.HtmlPanelGroup;

/**
 * <p>
 * The TreeBean class is the backing bean for the Tree Component showcase
 * demonstration. It is used to store and display the selected tree node
 * </p>
 * 
 * @see NodeUserObject
 * @since 0.3.0
 */

@ManagedBean
@SessionScoped
public class TreeController implements Serializable {

	private static final long serialVersionUID = 1L;

	private static final int MCUS_ID = -1;
	private static final int GATEKEEPERS_ID = -2;
	private static final int ROOMS_ID = -4;
	private static final int USERS_ID = -5;
	private static final int ROOT_ID = -3;

	public static final String NODE_ID_PARAMETER_NAME = "nodeId";

	private static final long DOUBLECLICK_LENGTH = 600;

	// tree default model, used as a value for the tree component
	private DefaultTreeModel model;
	private NodeUserObject selectedUserObject;

	@ManagedProperty(value = "#{engine}")
	private volatile Engine engine;

	@ManagedProperty(value = "#{tabs}")
	private Tabs tabs;

	private long lastModelUpdate = 0;

	/**
	 * Construct the default tree structure by combining tree nodes.
	 */
	public TreeController() {
		init();
	}

	public DefaultTreeModel getModel() {
		
		// budowanie drzewa
		
		// jeśli model się nie zmienił to ok
		if (!engine.area.hasModelChangedSince(lastModelUpdate)) {
			return model;
		}

		//cleanInactive();

		// zapisujemy czas updatu
		lastModelUpdate = System.currentTimeMillis();
		
		// gatekeepery
		DefaultMutableTreeNode gkNode = findTreeNode(GATEKEEPERS_ID), tmp;
		for (Gatekeeper x : engine.area.gks) {
			// dodawanie nowych nodów z gatekeeperami
			if (findTreeNode(x.hashCode()) == null) {
				addNode(gkNode, getGkLabel(x), x.hashCode(), false);
				// tabs.openTab(x.hashCode());
			}
		}
		for (Enumeration<DefaultMutableTreeNode> e = gkNode.children(); e
				.hasMoreElements();) {
			tmp = e.nextElement();
			// przechodzimy po istniejących gatekeeperach
			Gatekeeper g = engine.area.getElement(
					((NodeUserObject) tmp.getUserObject()).getId(),
					Gatekeeper.class);
			
			// jeśli już ich niema w modelu to usuwamy
			if (g == null) {
				tmp.removeFromParent();
				continue;
			}
			
			// userzy w gatekeeperach, każdy user pod swoim gk
			for (User u : g.users) {
				if (findTreeNode(u.hashCode()) == null) {
					addNode(tmp,
							(u.getAlias() == null ? "unknown" : u.getAlias())
									+ "(" + u.ip + ":" + u.port + ")",
							u.hashCode(), true);
				}
			}
			((NodeUserObject) tmp.getUserObject()).setText(getGkLabel(g));
		}

		// etykieta przy nodzie z gatekeeperami czyli ich ilość
		((NodeUserObject) gkNode.getUserObject()).setText("Gatekeppers ("
				+ engine.area.gks.size() + ")");

		// mcu
		gkNode = findTreeNode(MCUS_ID);
		for (Mcu x : engine.area.mcus) {
			// dodawanie nowych mcu
			if (findTreeNode(x.hashCode()) == null) {
				addNode(gkNode, getMcuLabel(x), x.hashCode(), false);
				// tabs.openTab(x.hashCode());
			}
		}

		for (Enumeration<DefaultMutableTreeNode> e = gkNode.children(); e
				.hasMoreElements();) {
			tmp = e.nextElement();
			Mcu m = engine.area.getElement(
					((NodeUserObject) tmp.getUserObject()).getId(), Mcu.class);
			// usunięcie nieistniejących mcu
			if (m == null) {
				tmp.removeFromParent();
				continue;
			}
			// pokoje w mcu
			for (Room r : m.rooms) {
				if (findTreeNode(r.hashCode()) == null) {
					addNode(tmp, getRoomLabel(r), r.hashCode(), true);
				}
			}
			((NodeUserObject) tmp.getUserObject()).setText(getMcuLabel(m));
		}

		((NodeUserObject) gkNode.getUserObject()).setText("MCUs ("
				+ engine.area.mcus.size() + ")");

		// root
		gkNode = findTreeNode(ROOT_ID);
		((NodeUserObject) gkNode.getUserObject())
				.setText(getAreaLabel(engine.area));

		// System.out.println("GET MODEL");

		return model;
	}

	/**
	 * Usunięcie z drzewa wierzchołków reprezentujących nieaktywnye kontrolery
	 * */
	private void cleanInactive() {
		DefaultMutableTreeNode tmp;
		for (Enumeration<DefaultMutableTreeNode> e = findTreeNode(ROOT_ID)
				.breadthFirstEnumeration(); e.hasMoreElements();) {
			tmp = e.nextElement();
			Object o = engine.area.getElement(((NodeUserObject) tmp
					.getUserObject()).getId());
			if (o instanceof Gatekeeper
					&& !((Gatekeeper) o).controller.isActive()) {
				tmp.removeFromParent();
				if (o instanceof Mcu && !((Mcu) o).controller.isActive()) {
					tmp.removeFromParent();
				}
			}
		}

	}

	public void setModel(DefaultTreeModel model) {
		this.model = model;
	}

	public NodeUserObject getSelectedUserObject() {
		return selectedUserObject;
	}

	// niby ze doubleclick
	private Long lastClick = 0L;

	public void employeeNodeSelected(ActionEvent event) {
		String employeeId = FacesContext.getCurrentInstance()
				.getExternalContext().getRequestParameterMap()
				.get(NODE_ID_PARAMETER_NAME);

		if (employeeId == null) {
			return;
		}
		int id = Integer.valueOf(employeeId);
		DefaultMutableTreeNode node = findTreeNode(id);
		selectedUserObject = (NodeUserObject) node.getUserObject();

		// doubleclick
		if (System.currentTimeMillis() - lastClick < DOUBLECLICK_LENGTH) {
			tabs.openTab(id);
			lastClick = 0L;
		} else {
			lastClick = System.currentTimeMillis();
		}

	}

	public boolean isMoveUpDisabled() {
		DefaultMutableTreeNode selectedNode = selectedUserObject.getWrapper();
		return isMoveDisabled(selectedNode, selectedNode.getPreviousNode());
	}

	public boolean isMoveDownDisabled() {
		DefaultMutableTreeNode selectedNode = selectedUserObject.getWrapper();
		return isMoveDisabled(selectedNode, selectedNode.getNextNode());
	}

	public boolean isMoveDisabled(DefaultMutableTreeNode selected,
			DefaultMutableTreeNode swapper) {
		return selected == null || swapper == null
				|| selected.getAllowsChildren() || swapper.isRoot();
	}

	public void moveUp(ActionEvent event) {
		DefaultMutableTreeNode selectedNode = selectedUserObject.getWrapper();
		exchangeNodes(selectedNode.getPreviousNode(), selectedNode);
	}

	public void moveDown(ActionEvent event) {
		DefaultMutableTreeNode selectedNode = selectedUserObject.getWrapper();
		exchangeNodes(selectedNode, selectedNode.getNextNode());
	}

	public void exchangeNodes(DefaultMutableTreeNode node1,
			DefaultMutableTreeNode node2) {
		DefaultMutableTreeNode node1Parent = (DefaultMutableTreeNode) node1
				.getParent();
		DefaultMutableTreeNode node2Parent = (DefaultMutableTreeNode) node2
				.getParent();
		DefaultMutableTreeNode node1PrevNode = node1.getPreviousNode();
		DefaultMutableTreeNode node1PrevNodeParent = (DefaultMutableTreeNode) node1PrevNode
				.getParent();
		int childCount = 0;

		if (node1.isNodeDescendant(node2)) {
			while (node2.getChildCount() > 0) {
				node1.insert((MutableTreeNode) node2.getFirstChild(),
						childCount++);
			}
			if (node1PrevNode == node1Parent
					|| (node1PrevNode.isNodeSibling(node1) && !node1PrevNode
							.getAllowsChildren())) {
				node1Parent.insert(node2, node1Parent.getIndex(node1));
			} else if (node1PrevNode.getAllowsChildren()) {
				node1PrevNode.add(node2);
			} else {
				node1PrevNodeParent.add(node2);
			}

			return;
		}

		if (node2.getAllowsChildren()) {
			node2.insert(node1, 0);
		} else {
			node1.removeFromParent();
			node2Parent.insert(node1, node2Parent.getIndex(node2) + 1);
		}
	}

	public void dropListener(DropEvent event) {
		HtmlPanelGroup panelGroup = (HtmlPanelGroup) event.getComponent();

		DefaultMutableTreeNode dragNode = (DefaultMutableTreeNode) event
				.getTargetDragValue();
		DefaultMutableTreeNode dropNode = (DefaultMutableTreeNode) panelGroup
				.getDropValue();
		DefaultMutableTreeNode dropNodeParent = (DefaultMutableTreeNode) dropNode
				.getParent();

		if (dragNode.isNodeDescendant(dropNode))
			return;

		if (dropNode.getAllowsChildren()) {
			dropNode.insert(dragNode, 0);
		} else {
			dragNode.removeFromParent();
			dropNodeParent.insert(dragNode,
					dropNodeParent.getIndex(dropNode) + 1);
		}
	}

	private DefaultMutableTreeNode addNode(final DefaultMutableTreeNode parent,
			final String title, final int id, final boolean leaf) {
		DefaultMutableTreeNode node = new DefaultMutableTreeNode();
		NodeUserObject userObject = new NodeUserObject(node);
		node.setUserObject(userObject);
		userObject.setId(id);

		userObject.setText(title);
		userObject.setLeaf(leaf);
		userObject.setExpanded(ROOT_ID == id);
		node.setAllowsChildren(!leaf);
		// finally add the node to the parent.

		if (parent != null) {
			parent.add(node);
		}

		return node;
	}

	protected void init() {
		DefaultMutableTreeNode rootNode = addNode(null, "H323 Domain", ROOT_ID,
				false);
		model = new DefaultTreeModel(rootNode);
		selectedUserObject = (NodeUserObject) rootNode.getUserObject();
		selectedUserObject.setExpanded(true);
		addNode(rootNode, "Gatekeepers (0)", GATEKEEPERS_ID, false);
		addNode(rootNode, "MCUs (0)", MCUS_ID, false);
	}

	/**
	 * @return node o zadanymi id z obiektu NodeUserObject
	 * */
	private DefaultMutableTreeNode findTreeNode(long nodeId) {
		DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode) model
				.getRoot();
		DefaultMutableTreeNode node;
		NodeUserObject tmp;
		Enumeration nodes = rootNode.depthFirstEnumeration();
		while (nodes.hasMoreElements()) {
			node = (DefaultMutableTreeNode) nodes.nextElement();
			tmp = (NodeUserObject) node.getUserObject();
			if (nodeId == tmp.getId()) {
				return node;
			}
		}
		return null;
	}

	public String getParamId() {
		return NODE_ID_PARAMETER_NAME;
	}

	public void setEngine(Engine engine) {
		this.engine = engine;
	}

	public void setTabs(Tabs tabs) {
		this.tabs = tabs;
	}

	/**
	 * @param mcu
	 * @return etykietę w drzewku przy mcu
	 */
	private final String getMcuLabel(final Mcu mcu) {
		final int s = mcu.rooms.size();
		return mcu.ip + ":" + mcu.port + " (" + s
				+ ((s == 1) ? " room" : " rooms") + ")";
	}

	/**
	 * @param g
	 * @return etykietę w drzewku przy gkeeperze
	 */
	private final String getGkLabel(final Gatekeeper g) {
		final int s = g.users.size();
		return g.ip + ":" + g.port + " (" + s + ((s == 1) ? " user" : " users")
				+ ")";
	}

	/**
	 * @param area
	 * @return etykietę w drzewku dla domeny
	 */
	private final String getAreaLabel(final H323Area area) {
		final int u = area.users.size();
		final int r = area.rooms.size();
		return "H323 Domain (" + u + ((u == 1) ? " user" : " users") + ", " + r
				+ ((r == 1) ? " room" : " rooms") + ")";
	}

	/**
	 * @param area
	 * @return etykieta w drzewku dla pokoju
	 */
	private final String getRoomLabel(final Room room) {
		final int u = room.members.size();
		return room.alias + " (" + u + ((u == 1) ? " member" : " members")
				+ ")";
	}

}
